//
//  ADRouter.swift
//  ADKit
//
//  Created by Adoma on 2020/10/25.
//

import SwiftyJSON

public typealias ADRouterKey = String
public extension ADRouterKey {
    static let isPresentTransAction = "isPresentTransAction"
    static let presentCompletedBlock = "presentCompletedBlock"
    static let transActionNoAnimated = "transActionNoAnimated"
}

public typealias ADRouterParam = [ADRouterKey: Any]
public typealias ADPresentCompletedBlock = ()->Void

public protocol ADRouterProtocol {
    
    /// 遵守协议就必须实现创建方法，返回nil不做跳转
    static func create(param: ADRouterParam?) -> UIViewController?
    
    /// 连续相同类跳转，返回NO打断跳转
    func sameTargetClassDecide(with: UIViewController) -> Bool?
    
    /// 在路由跳转前调用，返回YES为强制present跳转
    func specifyPresentTransAction() -> Bool?
    
    /// 跳转前调用，返回NO则不进行跳转
    func willRouted(from: UIViewController) -> Bool
}

extension ADRouterProtocol {
    
    public func sameTargetClassDecide(with: UIViewController) -> Bool? {
        return nil
    }
    
    public func specifyPresentTransAction() -> Bool? {
        return nil
    }
    
    public func willRouted(from: UIViewController) -> Bool {
        return true
    }
}

fileprivate var adrouterparamkey: UInt8 = 0

extension UIViewController {
    
    public var routerParam: ADRouterParam? {
        set {
            objc_setAssociatedObject(self, &adrouterparamkey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        get {
            return objc_getAssociatedObject(self, &adrouterparamkey) as? ADRouterParam
        }
    }
}

/// by ADKit
public struct ADRouter {
    
    public struct RoutMap {
        
        public let scheme: String
        
        /// path: Class
        public let mapping: [String: String]
        
        public init(scheme: String, mapping: [String: String]) {
            self.scheme = scheme
            self.mapping = mapping
        }
    }
    
    public static func initialize(map: RoutMap) {
        shared.map = map
    }
    
    public typealias Target = UIViewController
    
    static var shared = ADRouter()
    var map: RoutMap?
    
    @discardableResult
    public static func routeWith(urlString: String) throws -> Target {
        
        guard let map = shared.map else {
            throw ADError.error(message: "ADRouter RoutMap not initialized!")
        }
        
        // scheme://host/path?query(k=v&k=v)
        // host - route
        // path0 - module
        // path1 - cls
        guard let url = urlString.toURL, url.scheme == map.scheme else {
            throw ADError.error(message: "ADRouter \(map.scheme) not support \(urlString)!")
        }
        
        guard let clsName = JSON(map.mapping)[url.path].string else {
            throw ADError.error(message: "ADRouter \(url.path) not matched!")
        }
        
        return try routeWith(clsName: clsName, param: url.queryDictionary)
    }
    
    @discardableResult
    public static func routeWith(clsName: String, param: ADRouterParam? = nil, module: String? = nil) throws -> Target {
        
        let moduleName = module ?? Bundle.main.name
        
        let fullClsName = moduleName + "." + clsName
        
        guard let targetCls = NSClassFromString(fullClsName) else {
            throw ADError.error(message: "ADRouter [\(clsName)] is not defined!")
        }
        
        var target: Target?
        
        if let cls =  targetCls as? ADRouterProtocol.Type {
            target = cls.create(param: param)
        } else if let cls = targetCls as? Target.Type {
            target = cls.init()
        }
        
        guard let target = target else {
            throw ADError.error(message: "ADRouter [\(clsName)] is not created!")
        }
        
        try routeWith(target: target, param: param)
        
        return target
    }
    
    public static func routeWith(target: Target, param: ADRouterParam? = nil) throws {
        
        target.routerParam = param?.reduce(into: [:], { (result, param) in
            let key = param.key
            switch key {
            case .isPresentTransAction,
                .transActionNoAnimated,
                .transActionNoAnimated:
                return
            default:
                result?[key] = param.value
            }
        })
        
        guard let current = WindowManager.currentViewController else {
            throw ADError.error(message: "ADRouter not find currentViewController!")
        }
        
        let isProtocol = target as? ADRouterProtocol
        
        if current.self == target.self, isProtocol?.sameTargetClassDecide(with: current) == false {
            return
        }
        
        var isPresent = param?[.isPresentTransAction] as? Bool ?? false
        
        if isProtocol?.specifyPresentTransAction() == true {
            isPresent = true
        }
        
        let isNoAnimated = param?[.transActionNoAnimated] as? Bool ?? false
        
        if isPresent {
   
            if isProtocol?.willRouted(from: current) == false {
                return
            }
            
            let completionBlock = param?[.presentCompletedBlock] as? ADPresentCompletedBlock
            
            target.modalPresentationStyle = .overFullScreen

            current.present(target, animated: !isNoAnimated, completion: completionBlock)
            
        } else {
            
            guard let navi = current.navigationController else {
                throw ADError.error(message: "ADRouter not find navigationController!")
            }
            
            if isProtocol?.willRouted(from: current) == false {
                return
            }
            
            navi.pushViewController(target, animated: !isNoAnimated)
        }
        
    }
    
}
